//
// Created by Japhy on 2017-8-31.
//

#define LOG_TAG "ccid"
#define DEBUG 1

#if DEBUG
#include "alog.h"
#define D LOGD
#else
#define D(...)
#endif

#include "ccid.h"
#include "usb.h"
#include <linux/types.h>	/* __u8 etc */
#include <asm/byteorder.h>	/* le16_to_cpu */
#include <string.h>

#define CCID_TIMEOUT    10000

struct ccid_command {
    __u8 bMessageType;
    __le32 wLength;
    __u8 bSlot;
    __u8 bSeq;
    __u8 bSpecific_0;
    __u8 bSpecific_1;
    __u8 bSpecific_2;
    __u8 abData[2048];
} __attribute__ ((packed));

struct ccid_response {
    __u8 bMessageType;
    __le32 wLength;
    __u8 bSlot;
    __u8 bSeq;
    __u8 bStatus;
    __u8 bError;
    __u8 bSpecific;
    __u8 abData[2048];
} __attribute__ ((packed));

static uint8_t ccid_next_seq(void){
    static uint8_t seq = 0;
    return seq++;
}

static void ccid_build_command
        (struct ccid_command* command, uint8_t message,
         uint8_t data[], uint32_t length){
    memset(command, 0, 10);
    command->bMessageType = message;
    command->bSeq = ccid_next_seq();
    command->wLength = __cpu_to_le32(length);
    memcpy(command->abData, data, length);
}

static int ccid_get_response(struct ccid_response* response,
    uint8_t data[], uint32_t* length){
    *length = __le32_to_cpu(response->wLength);
    memcpy(data, response->abData, *length);
    return response->bStatus;
}

static int ccid_write_command(struct ccid_command* command){
    int ret;
    uint8_t* cmd = (uint8_t *)command;
    uint32_t total = __le32_to_cpu(command->wLength) + 10;
    uint32_t offset = 0;

    while(offset < total){
        ret = usb_write(cmd + offset, total - offset, CCID_TIMEOUT);
        if(ret < 0){
            return ret;
        }
        offset += ret;
    }

    return 0;
}


static int ccid_is_response_complete(struct ccid_response* response, uint32_t length){
    return ((uint32_t)__le32_to_cpu(response->wLength) + 10) == length;
}

static int ccid_read_response(struct ccid_response* response){
    int ret;
    uint8_t* resp = (uint8_t *)response;
    uint32_t offset = 0;

    response->wLength = 0;
    while(!ccid_is_response_complete(response, offset)){
        ret = usb_read(resp + offset, sizeof(struct ccid_response), CCID_TIMEOUT);
        if(ret < 0){
            return ret;
        }
        offset += ret;
    }

    return 0;
}

static int ccid_xfer(struct ccid_command* command, struct ccid_response* response){
    int ret = ccid_write_command(command);

    if(ret < 0){
        return ret;
    }
    ret = ccid_read_response(response);
    if(ret < 0){
            return ret;
    }
    if(command->bSeq != response->bSeq){
        return -2;
    }
    return 0;
}

int ccid_connect(uint8_t *atr, uint32_t *alen){
    uint8_t cmd[10];
    uint8_t resp[10+32];
    int ret;

    ccid_build_command((struct ccid_command*)cmd, 0x62, NULL, 0);
    ret = ccid_xfer((struct ccid_command*)cmd, (struct ccid_response*)resp);
    if(ret < 0){
        return ret;
    }
    ccid_get_response((struct ccid_response*)resp, atr, alen);
    return 0;
}

int ccid_exchange(uint8_t *capdu, uint32_t clen, uint8_t *rapdu, uint32_t *rlen){
    uint8_t cmd[2048];
    uint8_t resp[2048];
    int ret;

    if(clen > 256){
        return -1;
    }

    *rlen = 0;
    ccid_build_command((struct ccid_command*)cmd, 0x6f, capdu, clen);
    ret = ccid_xfer((struct ccid_command*)cmd, (struct ccid_response*)resp);
    if(ret < 0){
        return ret;
    }
    ccid_get_response((struct ccid_response*)resp, rapdu, rlen);
    return 0;
}

int ccid_disconnect(void){
    uint8_t cmd[16];
    uint8_t resp[16];
    int ret;

    ccid_build_command((struct ccid_command*)cmd, 0x63, NULL, 0);
    ret = ccid_xfer((struct ccid_command*)cmd, (struct ccid_response*)resp);
    if(ret < 0){
        return ret;
    }
    return 0;
}

int ccid_slot_status(void){
    D(__FUNCTION__);
    uint8_t cmd[16];
    uint8_t resp[16];
    uint8_t status;
    struct ccid_response* response = (struct ccid_response*)resp;
    int ret;

    ccid_build_command((struct ccid_command*)cmd, 0x65, NULL, 0);
    ret = ccid_xfer((struct ccid_command*)cmd, response);
    if(ret < 0){
        return 0;
    }
    status = response->bStatus & 0x03;
    return status == 0x00 || status == 0x01;
}